
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <stdio.h>
#include "stack.h"
#include "hash.h"
#include "list.h"
#include "secd.h"
#include "parser.h"
#include "gc.h"

tiny_bytecode *make_bytecode(enum opcodes op)
{
    tiny_bytecode *ret = (tiny_bytecode *)malloc(sizeof(tiny_bytecode));
    ret->next = NULL;
    ret->opcode = op;
    ret->op = NULL;
    /*
    ret->level = 0;
    ret->pos = 0;
    ret->branch1= NULL;
    ret->branch2= NULL;
    */
    return ret;
}

tiny_secd_dump *make_secd_dump(tiny_bytecode *c,Stack *s,tiny_secd_env *e){
    tiny_secd_dump *ret = (tiny_secd_dump *)malloc(sizeof(tiny_secd_dump));
    ret->c = c;
    ret->s = s;
    ret->e = e;
    return ret;
}
void display_bytecode(tiny_bytecode *bytecode)
{
    if (bytecode == NULL)
    {
        return;
    }
    switch (bytecode->opcode)
    {
    case i_add:
        printf("ADD\n");
        break;
    case i_sub:
        printf("SUB\n");
        break;
    case i_div:
        printf("DIV\n");
        break;
    case i_mul:
        printf("MUL\n");
        break;
    case i_mod:
        printf("MOD\n");
        break;
    case i_ld:
        printf("LD %d %d\n", bytecode->level, bytecode->pos);
        break;
    case i_ldc:
        printf("LDC");
        bytecode->op->tail=NULL;
        display_value(bytecode->op,0);
        break;
    case i_ldf:
        printf("LDF\n");
        printf("[\n");
        display_bytecode(bytecode->branch1);
        printf("]\n");
        break;
    case i_ap:
        printf("AP\n");
        break;
    case i_rtn:
        printf("RTN\n");
        break;
    case i_nil:
        printf("NIL\n");
        break;
    case i_cons:
        printf("CONS\n");
        break;
    case i_sel:
        printf("SEL\n");
        printf("[\n");
        display_bytecode(bytecode->branch1);
        printf("]\n");
        printf("[\n");
        display_bytecode(bytecode->branch2);
        printf("]\n");
        break;
    case i_join:
        printf("JOIN\n");
        break;
    case i_eq:
        printf("EQ\n");
        break;
    case i_gt:
        printf("GT\n");
        break;
    case i_lt:
        printf("LT\n");
        break;
    case i_ge:
        printf("GE\n");
        break;
    case i_le:
        printf("LE\n");
        break;
    case i_def:
        printf("DEF\n");
        break;
    case i_stop:
        printf("STOP\n");
        break;
    case i_ldn:
        printf("LDN ");
        bytecode->op->tail=NULL;
        display_value(bytecode->op,0);
        break;
    case i_ldfn:
        printf("LDFN\n");
        //display_value(bytecode->op,0);
        break;
    case i_dum:
        printf("DUM\n");
        break;
    case i_rap:
        printf("RAP\n");
        break;
    case i_tap:
        printf("TAP\n");
        break;
    case i_dap:
        printf("DAP\n");
        break;
    }
    display_bytecode(bytecode->next);
}

tiny_value *lookup_global(struct SecdVm *vm, char *name){
    tiny_value *val = (tiny_value *)findFromHash(vm->globalEnv,name);
    if(val == NULL){
        printf("cant find %s\n",name);
        exit(1);
    }
    return val;
}
tiny_value *lookup_local(tiny_secd_env *env, int level, int position){
    tiny_value *val = NULL;
    while(level > 0){
        if(env == NULL){
            printf("ld error\n");
            exit(1);
        }
        env=env->up_level;
        --level;
    }
    if(env == NULL){
        printf("ld error\n");
        exit(1);
    }
    val = getItem(env->local_table,position);
    if(val == NULL){
        printf("ld error\n");
        exit(1);
    }
    return val;
}

void regist_global_func(struct SecdVm *vm, char *name, c_func func){
    tiny_value *val = gc_make_func(vm,func);
    setToHash(vm->globalEnv,name,val);
}

tiny_secd_env *make_secd_env(tiny_secd_env *up){
    tiny_secd_env *r=(tiny_secd_env *)malloc(sizeof(tiny_secd_env));
    r->up_level = up;
    r->local_table = initList();
    return r;
}
void ins_add(struct SecdVm *vm)
{
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    tiny_value *ret = NULL;
    if(is_double(a) ){
        ret=gc_make_double(vm,a->value.double_);
        if(is_double(b)){
            ret->value.double_ += b->value.double_;
        }else{
            ret->value.double_ += b->value.int_;
        }
    }else{
        if(is_double(b)){
            ret=gc_make_double(vm,a->value.int_);
            ret->value.double_ += b->value.double_;
        }else{
            ret=gc_make_integer(vm,a->value.int_);
            ret->value.int_ += b->value.int_ ;
        }
    }
    push(vm->stack,ret); 
    vm->code = vm->code->next;
}
void ins_sub(struct SecdVm *vm)
{
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    tiny_value *ret = NULL;
    if(is_double(a) ){
        ret=gc_make_double(vm,a->value.double_);
        if(is_double(b)){
            ret->value.double_ -= b->value.double_;
        }else{
            ret->value.double_ -= b->value.int_;
        }
    }else{
        if(is_double(b)){
            ret=gc_make_double(vm,a->value.int_);
            ret->value.double_ -= b->value.double_;
        }else{
            ret=gc_make_integer(vm,a->value.int_);
            ret->value.int_ -= b->value.int_ ;
        }
    }
    push(vm->stack,ret); 
    vm->code = vm->code->next;
}

void ins_mul(struct SecdVm *vm)
{
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    tiny_value *ret = NULL;
    if(is_double(a) ){
        ret=gc_make_double(vm,a->value.double_);
        if(is_double(b)){
            ret->value.double_ *= b->value.double_;
        }else{
            ret->value.double_ *= b->value.int_;
        }
    }else{
        if(is_double(b)){
            ret=gc_make_double(vm,a->value.int_);
            ret->value.double_ *= b->value.double_;
        }else{
            ret=gc_make_integer(vm,a->value.int_);
            ret->value.int_ *= b->value.int_ ;
        }
    }
    push(vm->stack,ret); 
    vm->code = vm->code->next;
}

void ins_div(struct SecdVm *vm)
{
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    tiny_value *ret = NULL;
    if(is_double(a) ){
        ret=gc_make_double(vm,a->value.double_);
        if(is_double(b)){
            ret->value.double_ /= b->value.double_;
        }else{
            ret->value.double_ /= b->value.int_;
        }
    }else{
        if(is_double(b)){
            ret=gc_make_double(vm,a->value.int_);
            ret->value.double_ /= b->value.double_;
        }else{
            ret=gc_make_integer(vm,a->value.int_);
            ret->value.int_ /= b->value.int_ ;
        }
    }
    push(vm->stack,ret); 
    vm->code = vm->code->next;
}


void ins_gt(struct SecdVm *vm)
{
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    //tiny_value *ret = make_value(tiny_type_bool);
    int r = tiny_value_compare(a,b);
    //ret->value.bool_=r>0?1:0;
    tiny_value *ret = gc_make_bool(vm, r>0?1:0);
    push(vm->stack,ret); 
    vm->code = vm->code->next;
}
void ins_lt(struct SecdVm *vm)
{
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    //tiny_value *ret = make_value(tiny_type_bool);
    int r = tiny_value_compare(a,b);
    //ret->value.bool_=r<0?1:0;
    tiny_value *ret = gc_make_bool(vm, r<0?1:0);
    push(vm->stack,ret); 
    vm->code = vm->code->next;
}
void ins_eq(struct SecdVm *vm)
{
    //printf("==>%d\n",stackSize(vm->stack));
    //exit(1);
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    //tiny_value *ret = make_value(tiny_type_bool);
    int v=0;
    int r = tiny_value_compare(a,b);
    v=r==0?1:0;
    push(vm->stack,gc_make_bool(vm,v)); 
    vm->code = vm->code->next;
}
void ins_ge(struct SecdVm *vm)
{
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    //tiny_value *ret = make_value(tiny_type_bool);
    int r = tiny_value_compare(a,b);
    //ret->value.bool_=r>=0?1:0;
    push(vm->stack,gc_make_bool(vm, r>=0?1:0)); 
    vm->code = vm->code->next;
}
void ins_le(struct SecdVm *vm)
{
    tiny_value *b = (tiny_value *)pop(vm->stack);
    tiny_value *a = (tiny_value *)pop(vm->stack);
    //tiny_value *ret = make_value(tiny_type_bool);
    int r = tiny_value_compare(a,b);
    //ret->value.bool_=r<=0?1:0;
    push(vm->stack,gc_make_bool(vm, r<=0?1:0)); 
    vm->code = vm->code->next;
}

void ins_mod(struct SecdVm *vm)
{
    tiny_value *list = (tiny_value *)pop(vm->stack);
    tiny_value *ret = NULL;
    if(is_integer(list->value.list) && is_integer(list->value.list->tail->value.list)){
        ret=gc_make_integer(vm,list->value.list->value.int_);
        ret->value.int_ = list->value.list->value.int_ % list->value.list->tail->value.int_ ;
    }else{
        //error
    }
    push(vm->stack,ret); 
    vm->code = vm->code->next;
}


void ins_cons(struct SecdVm *vm)
{
    tiny_value *item = (tiny_value *)pop(vm->stack);
    tiny_value *list = (tiny_value *)pop(vm->stack);
    tiny_value *ret = NULL;
    //display_value(item,0); 
    if(is_nil(list)){
        ret = gc_make_empty_list(vm);
        ret->value.list = item;
        ret->length=1;
    }else{
        ret = list->value.list;
        while(ret->tail !=NULL){
            ret = ret->tail;
        }
        //item->tail = NULL;
        ret->tail = item;
        list->length += 1;
        ret = list;
    }
    push(vm->stack,ret);
    
    vm->code = vm->code->next;
}

void ins_def(struct SecdVm *vm)
{
    tiny_value *val = (tiny_value *)pop(vm->stack);
    tiny_value *name = (tiny_value *)pop(vm->stack);
    vm->code = vm->code->next;
    setToHash(vm->globalEnv,name->value.str,val);
    push(vm->stack,&vm->NIL); 
}

void ins_nil(struct SecdVm *vm)
{
    push(vm->stack,&vm->NIL); 
    vm->code = vm->code->next;
}
void ins_ldc(struct SecdVm *vm)
{
    vm->code->op->tail=NULL;
    //printf("==>%p\n",vm->code->op);
    //exit(1);
    push(vm->stack,vm->code->op);
    vm->code = vm->code->next;
}

void ins_ldf(struct SecdVm *vm)
{
    //tiny_value *lambda = make_value(tiny_type_lambda);
    tiny_value *lambda = gc_make_lambda(vm);
    lambda->value.secd = vm->code->branch1;
    lambda->value.env=vm->environment;
    push(vm->stack,lambda);
    vm->code = vm->code->next;
}

void ins_dum(struct SecdVm *vm)
{
    vm->environment=make_secd_env(vm->environment);
    vm->code = vm->code->next;
}

void ins_ld(struct SecdVm *vm)
{
    tiny_value *ret = NULL;
    int level = vm->code->level;
    int pos = vm->code->pos;
    tiny_secd_env *env=vm->environment;
    while(level > 0){
        env=env->up_level;
        --level;
    }
    ret = (tiny_value *)getItem(env->local_table,pos);
    //printf("==>%d\n",ListLength(env->local_table));
    //if(ret == NULL){
    //    exit(1);
    //}
    
    push(vm->stack,ret);
    vm->code = vm->code->next;
}

//普通调用
void ins_ap(struct SecdVm *vm)
{
    tiny_value *func = (tiny_value *)pop(vm->stack);
    tiny_value *args = (tiny_value *)pop(vm->stack);

    vm->code = vm->code->next;
    if(is_procedure(func)){
        ((c_func)func->value.procedure)(vm, args);
        return;
    }
    if (vm->dump->size >= 512){
        printf("too many calls\n");
        exit(-1);
    }
    //printf("=======>\n");
    tiny_secd_dump *d = make_secd_dump(vm->code,vm->stack,vm->environment);
    push(vm->dump,d);
    vm->code=func->value.secd;
    vm->stack=initStack();
    vm->environment=make_secd_env(func->value.env);
    int n= 0;
    tiny_value *list = args->value.list;
    tiny_value *tmp = NULL;
    while(list!=NULL){
        insertToList(vm->environment->local_table,list,n);
        ++n;
        tmp = list->tail;
        list->tail=NULL;
        list= tmp;
    }
}

//递归调用
void ins_rap(struct SecdVm *vm)
{
    tiny_value *func = (tiny_value *)pop(vm->stack);
    tiny_value *args = (tiny_value *)pop(vm->stack);
    vm->code = vm->code->next;
    if(is_procedure(func)){
        ((c_func)func->value.procedure)(vm, args);
        return;
    }
    
    tiny_secd_dump *d = make_secd_dump(vm->code,vm->stack,vm->environment);
    push(vm->dump,d);
    vm->code=func->value.secd;
    vm->stack=initStack();
    vm->environment= func->value.env ;
    int n= 0;
    tiny_value *list = args->value.list;
    tiny_value *tmp = NULL;
    while(list!=NULL){
        insertToList(vm->environment->local_table,list,n);
        ++n;
        tmp = list->tail;
        list->tail=NULL;
        list= tmp;
    }
}


//尾调用
void ins_tap(struct SecdVm *vm)
{
    tiny_value *func = (tiny_value *)pop(vm->stack);
    tiny_value *args = (tiny_value *)pop(vm->stack);
    vm->code = vm->code->next;
    if(is_procedure(func)){
        ((c_func)func->value.procedure)(vm, args);
        return;
    }
    tiny_secd_dump *d = NULL;//make_secd_dump(vm->code,vm->stack,vm->environment);
    //push(vm->dump,d);
    vm->code=func->value.secd;
    //releaseStack(vm->stack);
    //vm->stack=initStack();
    vm->environment=make_secd_env(func->value.env);
    int l = stackSize(vm->dump);
    while(l>0){
        d=(tiny_secd_dump *)getTop(vm->dump);
        if(d->e == NULL){
            pop(vm->dump);
            free(d);
        }else{
            break;
        }
        --l;
    }
    int n= 0;
    tiny_value *list = args->value.list;
    
    tiny_value *tmp = NULL;
    //l = ListLength(vm->environment->local_table);
    //l = list_length(args);
    while(list!=NULL ){
        insertToList(vm->environment->local_table,list,n);
        ++n;
        //--l;
        tmp = list->tail;
        list->tail=NULL;
        list= tmp;
    }
    //printf("=====>%d\n",ListLength(vm->environment->local_table));
}


//尾递归
void ins_dap(struct SecdVm *vm)
{
    tiny_value *func = (tiny_value *)pop(vm->stack);
    tiny_value *args = (tiny_value *)pop(vm->stack);
    vm->code = vm->code->next;
    if(is_procedure(func)){
        ((c_func)func->value.procedure)(vm, args);
        return;
    }
    tiny_secd_dump *d = NULL;
    vm->code=func->value.secd;
    //printf("=======>\n");
    //display_value(args);
    //vm->environment=make_secd_env(func->value.env);
    int l = stackSize(vm->dump);
    while(l>0){
        d=(tiny_secd_dump *)getTop(vm->dump);
        if(d->e == NULL){
            pop(vm->dump);
            free(d);
        }else{
            break;
        }
        --l;
    }
    int n= 0;
    tiny_value *list = args->value.list;
    
    tiny_value *tmp = NULL;
    while(list!=NULL ){
        insertToList(vm->environment->local_table,list,n);
        ++n;
        tmp = list->tail;
        list->tail=NULL;
        list= tmp;
    }
}

void ins_rtn(struct SecdVm *vm)
{
    tiny_value *ret = (tiny_value *)pop(vm->stack);
    tiny_secd_dump *d = (tiny_secd_dump *)pop(vm->dump);
    vm->code = vm->code->next;
    releaseStack(vm->stack);
    vm->stack = d->s;
    vm->environment=d->e;
    vm->code = d->c;
    free(d);
    push(vm->stack,ret);
}


void ins_sel(struct SecdVm *vm)
{
    tiny_value *cond = (tiny_value *)pop(vm->stack);
    tiny_bytecode *next = NULL;
    if(cond->value.bool_){
        next=vm->code->branch1;
    }else{
        next=vm->code->branch2;
    }
    vm->code = vm->code->next;
    tiny_secd_dump *d = make_secd_dump(vm->code,NULL,NULL);
    push(vm->dump,d);
    vm->code = next;
    
}

void ins_join(struct SecdVm *vm)
{
    tiny_secd_dump *d = (tiny_secd_dump *)pop(vm->dump);
    //printf("==>%d\n",vm->dump->cp);
    vm->code = vm->code->next;
    vm->code = d->c;
    free(d);
}


void ins_ldn(struct SecdVm *vm)
{
    vm->code->op->tail=NULL;
    tiny_value *val = (tiny_value *)findFromHash(vm->globalEnv,vm->code->op->value.str);
    display_value(val, 0);
    push(vm->stack,val);
    vm->code = vm->code->next;
}


void SecdVm_init(struct SecdVm *vm)
{
    vm->IP = 0;
    vm->code = NULL;
    vm->dump = initStack();
    vm->stack = initStack();
    vm->globalEnv = initHNode();
    vm->environment = make_secd_env(NULL);
    vm->objects = NULL;
    vm->current = NULL;
    vm->NIL.type=tiny_type_nil;
    vm->count = 0;
    vm->freelist = NULL;
    vm->instructions[i_add] = ins_add;
        vm->instructions[i_sub] = ins_sub;
        vm->instructions[i_div] = ins_div;
        vm->instructions[i_mul] = ins_mul;
        vm->instructions[i_mod] = ins_mod;
        vm->instructions[i_ld] = ins_ld;
        vm->instructions[i_ldc] = ins_ldc;
        vm->instructions[i_ldf] = ins_ldf;
        vm->instructions[i_ap] = ins_ap;
        vm->instructions[i_tap] = ins_tap;
        vm->instructions[i_rtn] = ins_rtn;
        vm->instructions[i_nil] = ins_nil;
        vm->instructions[i_cons] = ins_cons;
        vm->instructions[i_sel] = ins_sel;
        vm->instructions[i_join] = ins_join;
        vm->instructions[i_eq] = ins_eq;
        vm->instructions[i_gt] = ins_gt;
        vm->instructions[i_lt] = ins_lt;
        vm->instructions[i_ge] = ins_ge;
        vm->instructions[i_le] = ins_le;
        vm->instructions[i_def] = ins_def;
    //    vm->instructions[i_stop] = ins_stop;
        vm->instructions[i_ldn] = ins_ldn;
        vm->instructions[i_ldfn] = ins_ldn;
        vm->instructions[i_dum] = ins_dum;
        vm->instructions[i_rap] = ins_rap;
        vm->instructions[i_dap] = ins_dap;
}


struct SecdVm *make_secd_vm(){
    struct SecdVm *r = (struct SecdVm *)malloc(sizeof(struct SecdVm));
    SecdVm_init(r);
    return r;
}

void SecdVm_run(struct SecdVm *vm,tiny_bytecode *code)
{
    vm->code = code;
    short instruction = 0;
    int count=0;
    while (1)
    {
        instruction = vm->code->opcode;
        count++;
        if (instruction == i_stop)
        {
            break;
        }
        else
        {
            ins_func func = vm->instructions[instruction];
            func(vm);
        }
    }
    printf("secd vm run %d instructions.\n",count);
}
